<?php
/**
 * ActiveRecord
 *
 * Класс ActiveRecord является базовым классом для всех объектов БД
 *
 * @author Кузьма Дмитрий <kouzma.d@gmail.com>
 * @version 1.1
 */
class ActiveRecord {
//ВАЖНЫЕ настройки сложных выборок/поисков:
//combine - многомерная выборка. Используется для поиска all //'combine' => array('sport' => 'ID_sport, sport_name', 'user' => 'login, ID_user')
	protected $tableName; 							// Название таблицы в БД
	protected $pkName;									// Название первичного ключа в БД
	public    $tableFields = array(); 	// Массив загруженных в объект полей
	public    $id = null;              	// Первичный ключ объекта
	private   $loaded = false; 					// Флаг загрузки объекта из БД
	protected $arrSettings;							// Массив дополнительных свойств объекта
	protected $arrSaveInclude = array();// Позволяет сохранять каскадно. Например амплуа для видов спорта array('PlayerRole')
	protected $arrSaveIncludeSettings = array(); // Настройки для массива $arrSaveInclude
	protected $tableDeleteFlag = 'is_deleted';					// Флаг псведоудаления в бд
	public    $action = null;              // Выполняемое действие 

  protected static $suggestLimit = 20; //ограничение на количество подсказываемых элементов

	private $stmt;

	private $select = '*';							//Образующийся сковый запрос
	private $conditions = null;					//Условия поиска (то, что пишется после WHERE)
	private $order = null;							//Условия сортировки - то, что пишется после ORDER BY
	private $group = null;							//Условия группировки - то, что пишется после GROUP BY
	private $limit = null;							//Ограничение на получение элементов в поиске
	private $having = null;							// Доп условие для поиска
	private $offset = 0;								//Позиция, начиная с которой следует выдавать поисковый результат
	private $include = array();					//Массив. Служит для добавления JOIN`ов к запросу
	private $includeList = array();			//Массив. Позволяет добавить список элементов для загрузки из БД. Например, амплуа для видов спорта array('player_role'). Используется для поиска одного элемента (можно прямо в конструкторе)
	private $combine = array();					//Массив. Позволяет настроить выборку так, что будут подключены доп. таблицы, а результат будет возвращен в виде многомерного маасива
																			//Имеет вид ('таблица' => 'поле1[, поле2]'[, 'таблица' => 'поле1[, поле2]'])
																			//'combine' => array('sport' => 'ID_sport, sport_name', 'user' => 'login, ID_user')
	private $joins = null;							//Строка. Позволяет вручную вставить подстроку JOIN ...
	private $getAuthor; 								//Для автоматического получения данных об авторе
	private $combineConverted = array();//Многомерный массив. Представляет собой массив $combine, в котором строки представлены массивами

	public $validatesAcceptanceOf;   // проверка установки флажка
	public $validatesAssociated;     // проверка связанных объектов
	public $validatesConfirmationOf; // проверяет совпадение с полем двойником
	public $validatesExclusionOf;    // проверяет отсутствие значения свойств в наборе
	public $validatesFormatOf;       // проверяет на соответствие регулярному выражению
	public $validatesInclusionOf;    // проверяет значения свойств на присутствие в наборе
	public $validatesLengthOf;       // проверяет длину значения свойства
	public $validatesNumericalityOf; // проверяет что свойство содержит правильное число
	public $validatesPresenceOf;     // Проверяет что свойство не имеет пустого значения
	public $validatesUniquenessOf;   // Проверяет свойство на уникальность
	protected $availableFields;      // Массив допустимых к сохранению полей
	
	protected $arrWysiwyg = array();


	public function __construct($arrInitialFields = null, $arrSettings = array()) {
	  Service_Debug::setDebug();
    if ($arrSettings) {
      foreach ($arrSettings as $settingName => $settingValue) {
  		  $this->setSetting($settingName, $settingValue);
  	  }
    }
  	if (is_numeric($arrInitialFields)) {
  	  // Осуществляем поиск и сохранение результатов
  	  $this->_findOne($arrInitialFields);
  	  if (isset($this->tableFields[$this->pkName])) {
		  	$this->id = $this->tableFields[$this->pkName];
  	  }
		}
	  elseif (is_string($arrInitialFields)) {
  	  // Осуществляем поиск и сохранение результатов
  	  $this->_findOne(Service_Escape::absolutelyEscapeString($arrInitialFields));
  	  if (isset($this->tableFields[$this->pkName])) {
		  	$this->id = $this->tableFields[$this->pkName];
  	  }
		}
		elseif(is_array($arrInitialFields)) {
  	  $this->tableFields = &$arrInitialFields;
  	  // Назначение PK
  	  if (!empty($this->tableFields[$this->pkName])) {
		  	$this->id = $this->tableFields[$this->pkName];
  	  }
		  // Убирание лишних символов WYSIWYG
  	  if ($this->arrWysiwyg) {
  	    foreach ($this->arrWysiwyg as $wysiwygField) {
  	    	if (isset($this->tableFields[$wysiwygField])) $this->tableFields[$wysiwygField] = stripcslashes($this->tableFields[$wysiwygField]);
  	    }
  	  }
		}
  }
  
  /**
   * Метод устанавливает значение поля
   *
   * @param string $fieldName - название поля
   * @param mixed $fieldValue - значение поля
   */
  public function setField($fieldName, $fieldValue) {
  	$this->tableFields[$fieldName] = $fieldValue;
  }
  
  /**
   * Метод устанавливает значение всех полей объекта, согласно переданному массиву
   *
   * @param array $tableFields
   */
  public function setFields($tableFields, $reload = false) {
  	if ($reload) $this->tableFields = &$tableFields;
  	else foreach ($tableFields as $key => $value) $this->tableFields[$key] = $value;
  }
  
  /**
   * Метод возвращает значение поля
   *
   * @param string $fieldName
   * @return mixed
   */
  public function getField($fieldName) {
  	if (isset($this->tableFields[$fieldName])) return $this->tableFields[$fieldName];
  	else return null;
  }
  
  /**
   * Метод проверяет вложенный объект
   *
   * @param array $arrList
   */
  public function validateInnerObject($arrFields, $objName) {
	  // Создаем новый объект для элемента
		$obj = new $objName($arrFields);

		$obj->tableFields[$this->pkName] = $this->id;
		if ($obj->getField($obj->pkName)) {
		  $obj->id = $obj->getField($obj->pkName);
		}
		$obj->fullValidation(array('errorOf' => array(get_class($this), $objName)));
  }

  /**
   * Метод полной проверки всех значений класса. Вызывает все необходимые подметоды проверки. beforeValidation и прочие.
   *
   * @param array $arrAdditionalSettings
   */
  public function fullValidation($arrAdditionalSettings = array()) {
    $this->beforeValidation($arrAdditionalSettings);
		if ($this->id) $this->beforeValidationOnUpdate();
  	else $this->beforeValidationOnCreate();
		$this->validation('save', $arrAdditionalSettings);
		if (!$this->id) $this->validation('create', $arrAdditionalSettings);
		else $this->validation('update', $arrAdditionalSettings);
		$this->afterValidation();
		if ($this->id) $this->afterValidationOnUpdate();
		else $this->afterValidationOnCreate();
  }
  
  /**
   * Проверяет на наличие недопустимых полей
   * 
   */
  public function checkAvailableFields() {
  	foreach ($this->_getArrToSave() as $fieldName => $val) { 
  		if (isset($this->availableFields[$fieldName])) { // Занесенные массивом (для персонализации сохранения по update/create
  			if (!$this->id && $this->availableFields[$fieldName] == 'update') { // Если мы добавляем, а допустимо только на апдейт
  			  Exception_ActiveRecord::addObjectException(get_class($this), $fieldName, Service_Translate::getText('Вы пытаетесь сохранить недопустимое поле', $fieldName));
  			}
  			elseif ($this->id && $this->availableFields[$fieldName] == 'create') { // Если мы редактируем, а допустимо только на create  
  			  Exception_ActiveRecord::addObjectException(get_class($this), $fieldName, Service_Translate::getText('Вы пытаетесь сохранить недопустимое поле', $fieldName));	
  			}
  		}
  		elseif (!in_array($fieldName, $this->availableFields)) { // Занесенные просто строкой (просто названия)
  		  Exception_ActiveRecord::addObjectException(get_class($this), $fieldName, Service_Translate::getText('Вы пытаетесь сохранить недопустимое поле', $fieldName));
  		}
  	}
  }
  
  /**
   * Метод проверяет список элементов класса
   *
   * @param array $arrList
   */
  public function validateList($arrList, $objName, $rememberFields = false) {
  	foreach ($arrList as $i => $element) { // Проходим по всем элементамсписка
  	  // Создаем новый объект для элемента
  		//$s = "\$obj = new " . $objName . "(\$element);";
  		//eval($s);
  		$obj = new $objName($element);

  		$obj->tableFields[$this->pkName] = $this->id;
			if ($obj->getField($obj->pkName)) {
			  $obj->id = $obj->getField($obj->pkName);
			  $arrIdsList[] = $obj->id;
			}
			
			if (!isset($this->arrSaveIncludeSettings[$objName]['onlyThrow']) || !$this->arrSaveIncludeSettings[$objName]['onlyThrow']) { // если мы сохраняем не только промежуточную таблицу
			 $arrParams = array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true);
			 if ($rememberFields) $arrParams['rememberField'] = true;
			 $obj->fullValidation($arrParams);
			}
			elseif (isset($this->arrSaveIncludeSettings[$objName]['required']) && $this->arrSaveIncludeSettings[$objName]['required']) { // Если что-то в списке обязательно должно быть
				if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
		  		if ($hasMany->tableName != $obj->tableName) $hasMany = null;
		  		else break;
		  	}
				$idSet = false;
		  	if ($arrList && $hasMany) foreach (Service_ArrayConverter::getArrayColumn($arrList, $hasMany->fkName) as $currentId) {
		  		if ($currentId) {
		  			$idSet = true;
						break;
					}
				}
				if (!$idSet) {
				  $arrSettings = array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true);
					$obj->errorValidated($objName, $arrSettings, $this->arrSaveIncludeSettings[$objName]['required']);
				}
			}
  	}
  }

  /**
   * Метод производит сохранение объекта
   *
   * @return array
   */
  public function save() {
    Service_Debug::setDebug();
  	// Устанавливаем флаг. Происходит добавление или редактирование.
    if (!$this->id) $isAdding = true;
  	else $isAdding = false;
  	// Осуществляем проверку данных перед сохранением
  	$this->fullValidation();
		//валидируем каскадные вещи
  	foreach ($this->tableFields as $key => $value) {
			if (Service_ArrayConverter::in_iarray($key, $this->arrSaveInclude)) {
					$this->validateList($value, $key);
   	  }
  	}
  	// Если валидация прошла успешно, то выполняем непосредственно сохранение вместе с сопутствующими методами вроде beforeSave
  	if (!empty($this->availableFields)) $this->checkAvailableFields();
  	if (!Exception_ActiveRecord::isExceptionOfModelExists(get_class($this))) {
  		$this->beforeSave();
  		if (!$this->id) $this->beforeCreate();
			else $this->beforeUpdate();
			$this->_save();
    	if ($isAdding) $this->afterCreate();
    	else $this->afterUpdate();
  		$this->afterSave();
  	}
  	else return false;
		return $this->id; // Возвращаем идентификатор сохраненной сущности
  }

  /**
   * Метод производит непосредственное сохранение объекта
   *
   * @return array
   */
  protected function _save() {
  	Service_Debug::setDebug();
  	// Если необходимо сохранять изображение (аватарку)
  	if (isset($this->arrSettings['image'])) {
      if (isset($this->image) && $this->image->isLoaded()) {
				$this->deleteAvatars();
				$this->image->setObject($this);
				$this->image->save();
				$this->tableFields = array_merge($this->tableFields, $this->image->getMainImageFolderAndFile());
			}
    }
		// Сохраняем непосредственно сам объект
		if (!$this->id) $this->add();
  	else $this->edit();
  	// Сохраняем картинки в БД
  	if (isset($this->arrSettings['image']) && isset($this->image) && $this->image->isLoaded()) {
  		$this->saveAvatar($this->image->getArrSizes());
  	}
  }
  /**
   * Метод добавляет объект в БД
   *
   * @return integer
   */
  protected function add() {
		$objDB = Service_Database::instance();//Получаем подключение к БД

		// Устанавливаем поля created_on / created_at, если нужно
		if (isset($this->arrSettings['created_at'])) $this->tableFields['created_at'] = Service_Date::gLocalTime2BD();
		if (isset($this->arrSettings['created_on'])) $this->tableFields['created_on'] = Service_Date::gLocalTime2BD();

		// Если необходимо сохранять теги
		if (isset($this->arrSettings['tags'])) {
			// Если теги заданы
		  if (!empty($this->tableFields['str_tags'])) {
			  //Формируем массив тегов
		    $arrTags = explode(',', str_replace(',,', ',', trim($this->tableFields['str_tags'], ', ')));
			  //Убираем в массиве тегов лишние символы
		    for ($i = 0; $i < sizeof($arrTags); $i++) $arrTags[$i] = trim($arrTags[$i]);
			  //Убираем повторяющиеся теги
		    $arrTags = array_unique($arrTags);
			  //Формируем новую строку тегов
		    $this->tableFields['str_tags'] = implode(', ', $arrTags);
			}
		}

		// Если необходимо сохранять автора
		if (isset($this->arrSettings['author'])) {
			$objSession = User_Session::instance();
			if ($objSession->getUserId()) {
				if ($this->arrSettings['author'] === true)
					$this->tableFields['ID_user'] = $objSession->getUserId();
				else $this->tableFields[$this->arrSettings['author']] = $objSession->getUserId();
			}
		}
		//Добавлем элемент в БД
		$this->id = $objDB->insertAutoincrement($this->tableName, $this->_getArrToSave());
		$this->tableFields[$this->pkName] = $this->id;
		return $this->id;
  }

  /**
   * Метод обновляет объект в БД
   *
   */
  protected function edit() {
    $objDB = Service_Database::instance();//Получаем подключение к БД
  	// Устанавливаем поля updated_on / updated_at, если нужно
		if (isset($this->arrSettings['updated_at']))	$this->tableFields['updated_at'] = Service_Date::gLocalTime2BD();
		if (isset($this->arrSettings['updated_on'])) $this->tableFields['updated_on'] = Service_Date::gLocalTime2BD();
		return $objDB->update($this->tableName, $this->_getArrToSave(), array($this->pkName => $this->id));
  }

  /**
   * Удаляет из массива лишние элементы для сохранения (например, _confirmation)
   *
   * @return array
   */
  private function _getArrToSave() {
  	$sizeFields = sizeof($this->tableFields);
  	$arrReturn = $this->tableFields;

  	// Убираем из сохраняемого массива то, что не относится непосредственно к сохраняемому
  	unset($arrReturn['save']);
  	unset($arrReturn['save_x']);
  	unset($arrReturn['save_y']);
  	unset($arrReturn['add']);
  	unset($arrReturn['add_x']);
  	unset($arrReturn['add_y']);
  	unset($arrReturn['edit']);
  	unset($arrReturn['edit_x']);
  	unset($arrReturn['edit_y']);

  	// Удаляем из массива чекбоксы вроде "согласен с правилами использования"
  	if ($this->validatesAcceptanceOf) foreach ($this->validatesAcceptanceOf as $keyToUnset => $value) {
			unset($arrReturn[$keyToUnset]);
		}

		// обработка полей
		foreach ($arrReturn as $key => $value) {
			if (is_array($value)) unset($arrReturn[$key]); // Убираем из сохранения массивы
			else {
				// Убираем из сохранения поля, оканчивающиеся на confirm
				$confirmPos = strpos($key, '_confirm');
			  if ($confirmPos) {
			  	if (isset( $this->validatesConfirmationOf[substr($key, 0, $confirmPos)] )) unset($arrReturn[$key]);
			  }
			  // Экранируем все вредоносные символы
			  if (!is_numeric($value) && $value !== null) $arrReturn[$key] = Service_Escape::absolutelyEscapeString($value);
			}
  	}
    // Делаем отдельную обработку на вредоносный код у полей WYSIWYG
  	foreach ($this->arrWysiwyg as $key => $value) {
  		if (isset($arrReturn[$value]))
  		  $arrReturn[$value] = Service_Escape::absolutelyEscapeStringFromWysiwyg($this->tableFields[$value]);
  	}
  	
		// Возвращаем сохраняемый массив значений
  	if (isset($arrReturn)) return $arrReturn;
  	else return $this->tableFields;
  }


  /**
   * Метод находит в БД объект/объекты
   *
   * @param string $itemsToFind - тип поиска - число (id)/all/first
   * @param array $arrSettings - свойства иска
   * @return array
   */
  public function find($itemsToFind, $arrSettings = array()) {
  	Service_Debug::setDebug();
		// устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
    $this->limit = null;
		$this->offset = 0;
		$this->conditions = null;

		// Устанавливаем все поисковые свойства
		foreach ($arrSettings as $settingName => $settingValue) {
			$this->setSetting($settingName, $settingValue);
  	}

  	//определяем тип поиска и делаем соответствующий вызов
		if (is_numeric($itemsToFind)) return $this->_findOne($itemsToFind);
		elseif ($itemsToFind == 'all') return $this->_findAll();
		elseif ($itemsToFind == 'first') return $this->_findFirst();
		else throw new Exception('The first parameter to the find method should be either an identifier or "all", or "first". You gave "' . $itemsToFind . '"');
	}

	/**
	 * Метод поиска одного элемента
	 *
	 * @param integer $idToFind - id для поиска
	 * @return array
	 */
	protected function _findOne($idToFind) {
	  Service_Debug::setDebug();
	  $objDB = Service_Database::instance();// Получаем подключение к БД

	  $this->conditions = "`" . $this->tableName . "`.`" . $this->pkName . "` = '" . $idToFind . "'"; // настраиваем поиск по айдишнику
		// устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
	  $this->limit = 1;
	  $this->offset = 0;

	  // осуществляем поиск и сохранение результатов
	  $this->_makeStmt();
	  $this->tableFields = $objDB->selectOne($this->stmt);
	  $this->id = isset($this->tableFields[$this->pkName]) ? $this->tableFields[$this->pkName] : null;
	  $this->loaded = true;
	  // Возвращаем сформированный результат
	  return $this->tableFields;
	}

	/**
	 * Метод поиска первого элемента в списке
	 *
	 * @return array
	 */
	private function _findFirst() {
	  Service_Debug::setDebug();
	  $objDB = Service_Database::instance();// Получаем подключение к БД
	  // устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
		$this->limit = 1;
	  $this->offset = 0;

	  // осуществляем поиск и сохранение результатов
	  $this->_makeStmt();
	  $this->tableFields = $objDB->selectOne($this->stmt);
	  $this->id = $this->getField($this->pkName);
	  $this->loaded = true;

	  // Возвращаем сформированный результат
	  return $this->tableFields;
	}

	/**
	 * Переводим массив подключаемых полей, к многомерному виду
	 *
	 */
	private function convertCombineToAbsArr() {
		// проходим по всему массиву $this->combine (т.е. проходим по подключаемым таблицам)
		foreach ($this->combine as $combineName => $combine) {
			$arrCombine = explode(',', $combine); // Получаем набор полей для каждой из подключаемых таблиц
			// Проходим по всем полям в каждой подключаемой таблице
			foreach ($arrCombine as $fieldName) {
		    $fieldName  = trim($fieldName, ' `');
		    $this->combineConverted[$combineName][] = $fieldName; // Записываем в многомерный массив-комбайн/ Будет выглядеть как combineConverted['connected_table']['field']
			}
		}
	}
	
	/**
	 * Метод поиска списка элементов
	 *
	 * @return array
	 */
	private function _findAll() {
	  Service_Debug::setDebug();
		$objDB = Service_Database::instance();// Получаем подключение к БД
		$this->_makeStmt();
		//echo $this->stmt;
		if (!$this->combine) { // Если обычная выборка
			return $objDB->select($this->stmt);
	  }
	  else { // Если комбинированная выборка, т.е. с выборкой из связных таблиц
	  	$arr = $objDB->select($this->stmt);	// Получаем данные об основной сущности вместе с данными о сопутствующих элементах
	  	$this->convertCombineToAbsArr(); 		// Переводим настройки комбайна в двумерный массив
	  	$i = 0;
	  	$arrRet = array();
	  	$arrLink = array();
	  	$pkName = $this->pkName;
	  	// Проходим по всем элементам основной сущности
	  	if ($arr) foreach ($arr as $row) {
	  		$id = $row[$pkName];
	  		if (!isset($arrLink[$id])) { // Если элемент встречается впервые
	  		  //Проходим по всем подключаемым таблицам (combineConverted['connected_table']['field'])
	  			foreach ($this->combineConverted as $combineName => $arrCombine) {
	  				if ($combineName != $this->tableName) {//Если это именно подключаемая таблица, а не собственная, тогда сохраняем все поля, относящиеся к этой таблице в отдельный массив
		  		  	
	  					//Проходим по всем полям подключаемой таблице
	  					foreach ($arrCombine as $fieldName) {
	  						$arrAdditional[$fieldName] = $row[$fieldName]; //Формируем подмассив для сохранения
	  					}

		  		  	$arrRet[$i][$combineName][] = $arrAdditional;  //Сохраняем подмассив
	  		  	}
	  		  	else{ // Если это собственная таблица, то просто переписываем все поля, относящиеся к этой таблице
	  		  		foreach ($arrCombine as $fieldName)	{$arrRet[$i][$fieldName] = $row[$fieldName];}
	  		  	}
	  		  }
	  		  $arrLink[$id] = $i; // Записываем ссылку на место, где сохранен соответствующий айдишник
	  		  $i++;
	  		}
	  		else { // Если элемент уже встречался
  			  $iTo = $arrLink[$id]; // Определяем, куда нужно сохранить элемент
	  			//Проходим по всем подключаемым таблицам
  			  foreach ($this->combineConverted as $combineName => $arrCombine) {
	  				if ($combineName != $this->tableName) {//Если это именно подключаемая таблица, а не собственная
		  		  	//Проходим по всем полям подключаемой таблице
		  		  	foreach ($arrCombine as $fieldName)	$arrAdditional[$fieldName] = $row[$fieldName]; //Формируем подмассив для сохранения
		  		  	$arrRet[$iTo][$combineName][] = $arrAdditional;  //Сохраняем подмассив
	  		  	}
	  			}
	  		}
	  	}
	  	return 	$arrRet;
	  }
	}

	/**
	 * Метод формирования поисковой строки
	 *
	 */
	private function _makeStmt() {
		$this->stmt = "SELECT " . (($this->combine && ($this->select == '*')) ? $this->_makeCombineSelect() : ($this->select == '*' ? '`' . $this->tableName . '`.*' : $this->select )) .
						($this->getAuthor ? ", `user`.`ID_user`, `is_admin`, `user_role`, `first_name`, `last_name`, `middle_name`, `login`, `user`.`pic_folder`, `user`.`pic_file`" : null) .
		        " FROM `" . $this->tableName . "`" .
		        ($this->joins ? (" " . $this->joins) : null) .
		        ($this->getAuthor ? " LEFT JOIN `user` ON `user`.`ID_user` = `" . $this->tableName . "`.`ID_user`" : null) .
		        ($this->conditions ? " WHERE " . $this->conditions : null) .
		        ($this->group ? " GROUP BY " . $this->group : null) .
		        ($this->having ? " HAVING " . $this->having : null) .
		        ($this->order ? " ORDER BY " . $this->order : null) .
		        (isset($this->limit) ? " LIMIT " . $this->offset . ", " . $this->limit : null);
		Service_Debug::log(get_class($this) . ' stmt', $this->stmt);
	}
	
	/**
	 * Метод формирования селекта из комбайна
	 *
	 */
	private function _makeCombineSelect() {
		$retStr = $this->tableName . '.*, ';
		if (isset($this->combine)) {
			foreach ($this->combine as $table => $fields) {
				$arrFields = explode(',', $fields);
				foreach ($arrFields as $field) {
					$retStr .= "`" . $table . '`.' . $field . ', ';
				}
			}
		}
		else return $retStr;
		return substr($retStr, 0, strlen($retStr) - 2);
	}
	
	/**
	 * Метод устанавливает свойство (для охранения)
	 *
	 * @param string $settingName
	 * @param mixed $settingValue
	 */
	public function setSaveSetting($settingName, $settingValue) {
		switch ($settingName) {
			case 'saveInclude':
				$this->setSaveInclude($settingValue);
				break;
			default:
				throw new Exception('You are trying to use invalid configuration ('. $settingName . '). To assume the settings are: "saveInclude".');
				break;
		}
	}
	
	/**
	 * Метод устанавливает свойство (поисковое)
	 *
	 * @param string $settingName
	 * @param mixed $settingValue
	 */
	public function setSetting($settingName, $settingValue) {
		switch ($settingName) {
			case 'select':
				$this->setSelect($settingValue);
				break;
			case 'conditions':
				$this->setConditions($settingValue);
				break;
			case 'order':
				$this->setOrder($settingValue);
				break;
		  case 'group':
				$this->setGroup($settingValue);
				break;
			case 'limit':
				$this->setLimit($settingValue);
				break;
			case 'having':
				$this->setHaving($settingValue);
				break;
			case 'offset':
				$this->setOffset($settingValue);
				break;
			case 'include':
				$this->setInclude($settingValue);
				break;
			case 'includeList':
			  $this->setIncludeList($settingValue);
				break;
			case 'combine':
				$this->setCombine($settingValue);
				break;
			case 'joins':
				$this->setJoins($settingValue);
				break;
			case 'author':
				$this->getAuthor = true;
				break;
			case 'id':
				$this->id = $settingValue;
				break;
			default:
				throw new Exception('You are trying to use invalid configuration ('. $settingName . '). The assume settings are: "select", "conditions", "joins", "include", "includeList", "order", "limit", "offset"');
				break;
		}
	}

	/**
	 * Устанавливает свойство поиска SELECT
	 *
	 * @param string $select
	 */
	private function setSelect($select) {
		$this->select = $select;
	}
	/**
	 * Устанавливает свойство поиска WHERE
	 *
	 * @param string $conditions
	 */
	private function setConditions($conditions) {
		$this->conditions = $conditions;
	}
	/**
	 * Устанавливает свойство поиска ORDER
	 *
	 * @param string $order
	 */
	private function setOrder($order) {
		$this->order = $order;
	}
	/**
	 * Устанавливает свойство поиска GROUP
	 *
	 * @param string $group
	 */
	private function setGroup($group) {
		$this->group = $group;
	}
	/**
	 * Устанавливает ограничение на количество элементов в поиске
	 *
	 * @param string $limit
	 */
	private function setLimit($limit) {
		$this->limit = intval($limit);
	}
	/**
	 * Устанавливает доп условие для выборки
	 *
	 * @param string $having
	 */
	private function setHaving($having) {
		$this->having = $having;
	}
	/**
	 * Устанавливает начальную позицию поиска
	 *
	 * @param string $offset
	 */
	private function setOffset($offset) {
		$this->offset = intval($offset);
	}
	/**
	 * Устанавливает какие связи нужно задействовать в запросе дополнительно
	 *
	 * @param string $include
	 */
	private function setInclude($include) {
		$this->include = $include;
	}
	/**
	 * Устанавливает какие связи нужно задействовать после запроса дополнительно
	 *
	 * @param string $includeList
	 */
	private function setIncludeList($includeList) {
	  $this->includeList = $includeList;
	}
	/**
	 * Устанавливает какбразом нужно скомбинировать результаты
	 *
	 * @param string $combine
	 */
	public function setCombine($combine) {
		$this->combine = $combine;
	}
	/**
	 * Устанавливает какие таблицы нужно соединить
	 *
	 * @param string $join
	 */
	public function setJoins($join) {
		$this->joins = $join;
	}
	/**
	 * Устанавливает какие коллекции нужно сохранить
	 *
	 * @param string $arrSaveInclude
	 */
	private function setSaveInclude($arrSaveInclude) {
		$this->arrSaveInclude = $arrSaveInclude;
	}
	/**
	 * Устанавливает какую коллекцию также надо сохранить
	 *
	 * @param mixed $saveInclude
	 */
	public function addSaveInclude($saveInclude) {
		if (is_string($saveInclude)) $this->arrSaveInclude[] = $saveInclude;
		elseif (is_array($saveInclude)) {
		  $arrSettings = &current($saveInclude);
			$this->arrSaveInclude[] = key($saveInclude);
			$this->arrSaveIncludeSettings[key($saveInclude)] = $arrSettings;
		}

	}

	protected function beforeValidation() {}
	protected function beforeValidationOnCreate() {}
	protected function beforeValidationOnUpdate() {}
	public function validation($validationType, $arrAdditionalSettings = array()) {
	  // Проверка установки флажка
		if ($this->validatesAcceptanceOf) foreach ($this->validatesAcceptanceOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
			  if ($validationType == 'save') $this->validateAcceptanceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) { $validationType; $this->validateAcceptanceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));}

		// Проверка на привязку к объекту
		if ($this->validatesAssociated) foreach ($this->validatesAssociated as $fieldToValidate => $arrToValidate) {
			if ( !isset($arrToValidate['on'])) {
		  	if ($validationType == 'save') $this->validateAssociated($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateAssociated($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
		}

		// Проверка на подтверждение данного поля
		if ($this->validatesConfirmationOf) foreach ($this->validatesConfirmationOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateConfirmationOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateConfirmationOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка на соответствие регулярному выражению
		if ($this->validatesFormatOf) foreach ($this->validatesFormatOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateFormatOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateFormatOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка длины поля
		if ($this->validatesLengthOf) foreach ($this->validatesLengthOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateLengthOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateLengthOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка на числовой ввод
		if ($this->validatesNumericalityOf) foreach ($this->validatesNumericalityOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateNumericalityOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateNumericalityOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

			// Проверка заполненности поля
		if ($this->validatesPresenceOf) foreach ($this->validatesPresenceOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validatePresenceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validatePresenceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

			// Проверка на уникальность значения
		if ($this->validatesUniquenessOf) foreach ($this->validatesUniquenessOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateUniquenessOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateUniquenessOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

	}
	protected function afterValidation() {}
	protected function afterValidationOnCreate() {}
	protected function afterValidationOnUpdate() {}
	protected function beforeSave() {}
	protected function beforeCreate() {}
	protected function beforeUpdate() {}
	protected function afterCreate() {}
	protected function afterUpdate() {}
	protected function afterSave() {}
	protected function beforeDestroy() {}
	protected function afterDestroy() {}

	protected function beforeDelete() {return true;}
	protected function afterDelete() {}
	protected function beforeDeleteAll($conditions) {}
	protected function afterDeleteAll($conditions) {}

	protected function operationInsert() {}
	protected function operationUpdate() {}
	protected function operationDestroy() {}

	public function errorValidated($fieldToValidate, &$arrToValidate, $alternativeMessage = '') {
		if (isset($arrToValidate['if'])) eval($arrToValidate['if']);
		if (isset($arrToValidate['rowException']) && $arrToValidate['rowException']) {
		  Exception_ActiveRecord::addRowException(isset($arrToValidate['errorOf']) ? $arrToValidate['errorOf'] : get_class($this), $alternativeMessage, isset($arrToValidate['complex']) ? $arrToValidate['complex'] : null);
		}
		else{
		  Exception_ActiveRecord::addObjectException(isset($arrToValidate['errorOf']) ? $arrToValidate['errorOf'] : get_class($this), $fieldToValidate, $alternativeMessage, isset($arrToValidate['complex']) ? $arrToValidate['complex'] : null);
		}
		return false;
	}

	// проверка установки флажка
	public function validateAcceptanceOf($fieldToValidate, &$arrToValidate = null) {
    $fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		if (!isset($arrToValidate['accept']) ) $arrToValidate['accept'] = 1;
		if (isset($this->tableFields[$fieldToValidate]) && !$arrToValidate['accept'] || !isset($this->tableFields[$fieldToValidate]) && $arrToValidate['accept'])
			return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 
			  (!$arrToValidate['accept'] 
			    ? Service_Translate::getText('поле не должно быть отмечено', $fieldName) 
			    : Service_Translate::getText('поле должно быть отмечено', $fieldName)
			  )
			);
		return true;
	}
	// проверка связанных объектов
	public function validateAssociated($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		// если такого поля нет или оно содержит null, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
    elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		// если задано регулярное выражение
		if (isset($arrToValidate['table'])) {
		}
		elseif (isset($arrToValidate['class'])) {
			$obj = new $arrToValidate['class']();
			$stmt = "SELECT `" . $obj->pkName . "` FROM `" . $obj->getTableName() . "` WHERE `" . $obj->pkName . "` = " . intval($this->tableFields[$fieldToValidate]);
  		$objDB = Service_Database::instance();//Получаем подключение к БД
  		if (!$objDB->isRecordExist($stmt))
  		  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('некорректно выбрано значение в поле', $fieldName));
		}
		return true;
	}
	// проверяет совпадение с полем двойником
	public function validateConfirmationOf($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate . '_confirmation', $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		if ($this->tableFields[$fieldToValidate] != $this->tableFields[$fieldToValidate . '_confirmation'])
			return $this->errorValidated($fieldToValidate . '_confirmation', $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не совпадает с его подтверждением', $fieldName));
	  return true;
	}
	// проверяет остутсвие значения свойств в наборе
	public function validateExclusionOf($fieldToValidate, $arrToValidate = null) {
		return true;
	}
	// проверяет на соответствие регулярному выражению
	public function validateFormatOf($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		// если задано регулярное выражение
		if (isset($arrToValidate['with'])) {
		  if (!preg_match($arrToValidate['with'], $this->tableFields[$fieldToValidate]))
		    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле заполнено неверно', $fieldName));
		}
		elseif (isset($arrToValidate['standard'])) {
			switch ($arrToValidate['standard']) {
				case 'humanDate':
					if (!Service_Date::isHumanDateCorrect($this->tableFields[$fieldToValidate]) || !Service_Date::humanDateToUnix($this->tableFields[$fieldToValidate]))
					  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле заполнено неверно', $fieldName));
					break;
				case 'dbDate':
					if (!Service_Date::isDBDateCorrect($this->tableFields[$fieldToValidate]))
					  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле заполнено неверно', $fieldName));
					break;
			}
		}
		return true;
	}

	// проверяет значения свойств на присутствие в наборе
	public function validateInclusionOf($fieldToValidate, $arrToValidate = null) {
		return true;
	}

	// проверяет длину значения свойства
	public function validateLengthOf($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		//$fieldLength = strlen($this->tableFields[$fieldToValidate]);
		$fieldLength = Service_Function::utf8_strlen($this->tableFields[$fieldToValidate]);

		// если задано точное совпадение
		if (isset($arrToValidate['is']))
		  if ($fieldLength != $arrToValidate['is'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['wrongLength']) ? $arrToValidate['wrongLength'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('неправильная длина поля', $fieldName, $arrToValidate['is']));

		// если задано минимальное ограничение
		if (isset($arrToValidate['minimum']))
		  if ($fieldLength < $arrToValidate['minimum']) {
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooShort']) ? $arrToValidate['tooShort'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('в поле слишком мало символов', $fieldName, $arrToValidate['minimum']));
		  }
		// если задано максимальное ограничение
		if (isset($arrToValidate['maximum']))
		  if ($fieldLength > $arrToValidate['maximum'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooLong']) ? $arrToValidate['tooLong'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('в поле слишком много символов', $fieldName, $arrToValidate['maximum']));
		return true;
	}
	
	// проверяет что свойство содержит правильное число
	public function validateNumericalityOf($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		if (!is_numeric($this->tableFields[$fieldToValidate]) || isset($arrToValidate['onlyInteger']) && !is_integer($this->tableFields[$fieldToValidate]))
	    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле должно быть числом', $fieldName));

		// если задано минимальное ограничение
		if (isset($arrToValidate['minimum']))
		  if ($this->tableFields[$fieldToValidate] < $arrToValidate['minimum']) {
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooShort']) ? $arrToValidate['tooShort'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('слишком малое значение поля', $fieldName, $arrToValidate['minimum']));
		  }
		// если задано максимальное ограничение
		if (isset($arrToValidate['maximum']))
		  if ($this->tableFields[$fieldToValidate] > $arrToValidate['maximum'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooLong']) ? $arrToValidate['tooLong'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('слишком большое значение поля', $fieldName, $arrToValidate['maximum']));
	  return true;
	}
	
	// Проверяет, что свойство не имеет пустого значения
	public function validatePresenceOf($fieldToValidate, $arrToValidate = null) {
    $fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		if (!isset($this->tableFields[$fieldToValidate]) || $this->tableFields[$fieldToValidate] === '' || !$this->tableFields[$fieldToValidate] && $this->tableFields[$fieldToValidate] !== 0) // если такого поля нет
	    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым'));
	  return true;
	}
	
	// Проверяет свойство на уникальность
	public function validateUniquenessOf($fieldToValidate, $arrToValidate = null) {
		$fieldName = isset($arrToValidate['fieldName']) ? '\'' . $arrToValidate['fieldName'] . '\'' : null;
		if (!(isset($arrToValidate['allowOneNull']) && $arrToValidate['allowOneNull'])) { // Если не выбран вариант, что доступна ровно одна пустая запись
			// если такого поля нет или оно содержит nool, которое не разрешено
			if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, (isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('поле не может быть пустым', $fieldName)));//(isset($arrToValidate['fieldName']) ? '"' . $arrToValidate['fieldName'] . '" ' : null)
			elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;
		}
		$uniqueInField = null;
		if (!empty($arrToValidate['uniqueInField'])) $uniqueInField = $arrToValidate['uniqueInField']; 
		if (!$this->isUnique($fieldToValidate, $this->tableFields[$fieldToValidate], $uniqueInField))return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : Service_Translate::getText('значение поля должно быть уникальным', $fieldName));
	  return  true;
	}
	
	/**
	 * Добавляет к массиву полей дополнительные поля
	 *
	 * @param array $arr
	 */
	public function merge($arr) {
		$this->tableFields = array_merge($this->tableFields, $arr);
	}

	/**
	 * Удаляет элемент по его идентификатору
	 *
	 * @param integer $id
	 * @return integer
	 */
  public function delete($id = null) {
    Service_Debug::setDebug();
    if (!$id) {
  	  if (isset($this->id)) $id = $this->id;
  	  else return false;
  	}
  	$objDB = Service_Database::instance();//Получаем подключение к БД
  	$objDB->startTransaction();
  	if (!$this->beforeDelete()) return false;
  	if (isset($this->arrSettings['image'])) $this->deleteAvatars();
  	
  	$stmt = "DELETE FROM `" . $this->tableName . "` WHERE `" . $this->pkName . "` = '" . intval($id) . "'";
  	$retValue = $objDB->query($stmt);
  	$this->afterDelete();
  	$objDB->stopTransaction();
  	return $retValue;
  }
  
	/**
	 * Проверяет, есть ли важные связи с другими объектами
	 *
	 * @return mixed - false, если связи не найдены
	 */
  public function getImportantEssenceLinks() {
    return false;
  }  

  /**
   * Удаляет список элементов
   *
   * @param string $conditions
   * @return integer
   */
	public function delete_all($conditions) {
	  Service_Debug::setDebug();
  	$this->beforeDeleteAll($conditions);
	  $objDB = Service_Database::instance();//Получаем подключение к БД
		if (isset($this->arrSettings['image']) || isset($this->arrSettings['ico']) || isset($this->arrSettings['tags'])) {
  		$stmt = "SELECT `avatar`.`ID_avatar`, `avatar`.`pic_file`, `avatar`.`pic_folder` 
  		FROM `avatar`
  		LEFT JOIN `" . $this->tableName . "` ON `avatar`.`ID_essence` = `" . $this->tableName . "`.`" . $this->pkName . "`
  		WHERE `avatar`.`essence_type` = '" . $this->tableName . "' AND (" . $conditions . ")";
  		$arrAvatars = $objDB->select($stmt);
  		if ($arrAvatars) {
  			foreach ($arrAvatars as $avatar) {
  				$fullPath = IMAGES . $avatar['pic_folder'] . $avatar['pic_file']; 
			    if (file_exists($fullPath) && is_file($fullPath)) unlink($fullPath);;
  			}
  			$stmt = "DELETE FROM `avatar`
  			WHERE `avatar`.`ID_avatar` IN (" . Service_Escape::intEscapeJoinOfPart(', ', $arrAvatars, 'ID_avatar') . ")";
  			$objDB->query($stmt);
  		}
  		/*if (isset($this->arrSettings['tags']) && $arrElements) {
  			$arrIds = array();
  			foreach ($arrElements as $element) {
  				$arrIds[] = $element[$this->pkName];
  			}
  			$objTags = new Tags_Tags();
        $objTags->deleteTagsOfEssenceArray($arrIds);
  		}*/
  	}
  	$stmt = "DELETE FROM `" . $this->tableName . "` WHERE " . $conditions;
  	$retVal = $objDB->query($stmt);
  	$this->afterDeleteAll($conditions);
  	return $retVal;
	}

	/**
	 * Подсчитывает количество элементов согласно условию
	 *
	 * @param string $conditions
	 */
	public function count($conditions = 1) {
		$objDB = Service_Database::instance();//Получаем подключение к БД
		$stmt = "SELECT COUNT(`" . $this->pkName . "`) FROM `" . $this->tableName . "` WHERE " . $conditions;
		return $objDB->selectSimpleField($stmt);
	}

	/**
	 * Метод возвращает подстроку для вставки LIMIT
	 *
	 * @param int $limit
	 * @param int $offset
	 * @return string
	 */
	public static function getLimitStr($limit = 1, $offset = 0) {
		return " LIMIT " . intval($offset) . ", " . intval($this->limit);
	}

  /**
   * Возвращает кол-во типовых страниц подходящих под условия
   *
   * @param $condition - Условия
   * @param $join - Подключаемые таблицы
   * @return
   */
  public function getElementsAmount($condition = null, $join = null) {
  	$arrCount = $this->find('all', array('select' => 'COUNT(`' . $this->tableName . '`.`' . $this->pkName . '`) AS count',
	  'joins' => $join,
	  'conditions' => $condition));
	  return $arrCount[0]['count'];
  }

  /**
   * Метод обновляет одно конкретное поле у таблицы
   *
   * @param string $fieldName - название поля в таблице
   * @param string $fieldValue - новое значение
   * @return bool
   */
  public function updateField($fieldName, $fieldValue) {
    Service_Debug::setDebug();
    if (!$this->id) return false;
    if (array_key_exists('updated_at', $this->tableFields)) {
			$stmtUpdatedAt = ", updated_at = '" . Service_Date::gLocalTime2BD() . "'";
		}
		else $stmtUpdatedAt = "";
		$objDB = Service_Database::instance();
		$this->setField($fieldName, $fieldValue);
		$stmt = "UPDATE `" . $this->tableName . "`
		SET `" . $this->tableName . "`.`" . $fieldName . "` = '" . $fieldValue . "'" . $stmtUpdatedAt . "
		WHERE `" . $this->tableName . "`.`" . $this->pkName . "` = '" . $this->id . "'";
		return $objDB->query($stmt);
  }
  
  /**
   * Проверка на последствия от изменения данного поля
   * 
   * @param string $fieldName - Название поля
   * @param string $fieldValue - Значение поля
   * 
   * @return mixed - false-нет последствий / другое значение - есть последствия
   */
  public function hasAftermath($fieldName, $fieldValue) {
    Service_Debug::setDebug();
    return false;
  }

  /**
   * Обновляет список конкретных полей в таблице
   *
   * @param string $arrFields - массив полей для редактирования. Ключ - название поля в БД. Значение - новое значение
   * @return bool
   */
  public function updateFields($arrFields) {
  	Service_Debug::setDebug();
    if (!$this->id || !$arrFields) return false;
		$objDB = Service_Database::instance();
		$stmt = "UPDATE `" . $this->tableName . "` SET ";
		$i = 0;
		foreach ($arrFields as $fieldName => $fieldValue) {
		 	if ($i++) $stmt .= ', ';
		 	$stmt .=  "`" . $this->tableName . "`.`" . $fieldName . "` = '" . Service_Escape::absolutelyEscapeString($fieldValue) . "'";
		}
		$stmt .= " WHERE `" . $this->tableName . "`.`" . $this->pkName . "` = '" . intval($this->id) . "'";
		return $objDB->query($stmt);
  }

  /**
   * Метод возвращает часть запроса в которой содержится условия поиска за период времени
   *
   * @param string $periodType
   * @return string
   */
  public static function getPeriodStmtWhere($periodType = '', $periodFieldName = '`created_at`') {
    switch ($periodType) {
      case 'year':
        return 'AND YEAR(' . $periodFieldName . ') = YEAR(CURRENT_DATE)';
        break;
      case 'month':
        return 'AND MONTH(' . $periodFieldName . ') = MONTH(CURRENT_DATE)';
        break;
      case 'week':
        return 'AND ' . $periodFieldName . ' > (DATE_SUB(NOW(), INTERVAL 7 DAY))';
        break;
      case 'day':
        return 'AND ' . $periodFieldName . ' > (DATE_SUB(NOW(), INTERVAL 1 DAY))';
        break;
      case 'total': default:
        return '';
        break;
	  }
  }
  
  /**
   * Проверка значения поля на уникальность
   * 
   * @param string $fieldName - Название поля
   * @param string $fieldValue - Значение поля
   * 
   * @return bool - true-уникальное/false-не уникальное
   */
  public function isUnique($fieldName, $value, $uniqueInField = null) {
  	Service_Debug::setDebug();
  	$objDB = Service_Database::instance();
  	$additionalConditions = "";
  	if ($uniqueInField) {
  		$uniqueInFieldValue = $this->getField($uniqueInField);
  	  if (empty($uniqueInFieldValue) && $this->id) {
  		  $stmt = "SELECT `" . $this->tableName . "`.`" . $uniqueInField . "` FROM `" . $this->tableName . "`
  		  WHERE `" . $this->pkName . "` = '" . $this->id . "'";
  		  $uniqueInFieldValue = $objDB->selectSimpleField($stmt);
  	  }
  	  $additionalConditions = " AND `" . $this->tableName . "`.`" . $uniqueInField . "` = '" . $uniqueInFieldValue . "' ";
  	}
		
		$stmt = "SELECT `" . $this->tableName . "`.`" . $this->pkName . "` FROM `" . $this->tableName . "`
			       WHERE `" . $this->tableName . "`.`" . $fieldName . "` = '" . $value . "'" . $additionalConditions;
		if ($this->id) $stmt .= " AND `" . $this->pkName . "` <> '" . $this->id . "'";
		return !$objDB->isRecordExist($stmt);				  
  }
  
  /**
   * Возвращает флаг загруженности объекта из БД
   *
   */
  public function isLoaded() {
    return $this->loaded;
  }
  
  /**
   * 
   * Удаляет аватарки, относящиеся к объекту
   */
  protected function deleteAvatars() {
  	Service_Debug::setDebug();	
  	if (!$this->id) return false;
		$objDB = Service_Database::instance();
		$stmt = "SELECT `avatar`.`pic_file`, `avatar`.`pic_folder` FROM `avatar`
			       WHERE `avatar`.`ID_essence` = '" . $this->id . "' AND `avatar`.`essence_type` = '" . $this->tableName . "'";
		$arrFiles = $objDB->select($stmt);
		if (!$arrFiles) return false;
		foreach ($arrFiles as $arrFile) {
			$fullPath = IMAGES . $arrFile['pic_folder'] . $arrFile['pic_file']; 
			if (file_exists($fullPath) && is_file($fullPath)) unlink($fullPath);;
		} 
		$stmt = "DELETE FROM `avatar`
						 WHERE `avatar`.`ID_essence` = '" . $this->id . "' AND `avatar`.`essence_type` = '" . $this->tableName . "'";
		return $objDB->query($stmt);
  }
  
  
  /**
   * Сохраняет информацию по аватарам в БД
   * 
   * @param array $arrSizes
   */
  protected function saveAvatar($arrSizes) {
  	Service_Debug::setDebug();
  	if ($arrSizes && $this->id) {
  		$stmtValues = '';
  		foreach ($arrSizes as $row => $arrSize) {
  			if ($row) $stmtValues .= ', ';
  			$stmtValues .= "('" . $this->id . "', '" . $this->tableName . "', '" . $arrSize['alias'] ."', '" . $arrSize['pic_folder'] ."', '" . $arrSize['pic_file'] ."')";
  		}
  		$stmt = "INSERT INTO `avatar` (`ID_essence`, `essence_type`, `avatar_alias`, `pic_folder`, `pic_file`) VALUES " . $stmtValues . ";";
  		$objDB = Service_Database::instance();
  		$objDB->query($stmt);
  	}	
  }
  
  /**
   * Возвращает имя таблицы
   */
  public function getTableName() {
    return $this->tableName;
  }
  
  /**
   * Обновляет теги в таблице с тегами
   * 
   */
  public function updateTags() {
  	Service_Debug::setDebug();
  	if (!$this->id) return false;
  	
    $arrTags = explode(',', str_replace(',,', ',', trim($this->tableFields['str_tags'], ', ')));
    for ($i = 0; $i < sizeof($arrTags); $i++) $arrTags[$i] = trim($arrTags[$i]);// Убираем в массиве тегов лишние символы
    $arrTags = array_unique($arrTags);// Убираем повторяющиеся теги
    
    $objDB = Service_Database::instance();
    $this->insertBaseTags($arrTags);
    // Получаем список ID тегов, которые есть в БД хоть у одной сущности
    $stmt = "SELECT `tag`.`ID_tag` FROM `tag` WHERE `tag`.`tag_name` IN ('" . join("', '", $arrTags) . "')";
    $arrTagIds = $objDB->selectSimpleArray($stmt);
    $stmt = "SELECT `essence_tag`.`ID_tag` FROM `essence_tag`
    WHERE `essence_tag`.`ID_essence` = '" . $this->id . "' AND `essence_tag`.`essence_type` = '" . $this->tableName . "';";
  	$arrStartTagIds = $objDB->selectSimpleArray($stmt);
  	
  	$arrNewTags = array_diff($arrTagIds, $arrStartTagIds);
  	$arrOldTags = array_diff($arrStartTagIds, $arrTagIds);
  	if ($arrOldTags) $this->deleteTags($arrOldTags);
  	if ($arrNewTags) $this->insertTags($arrNewTags);
    
	  //Формируем новую строку тегов
    $this->tableFields['str_tags'] = implode(', ', $arrTags);
  }
  
  /**
   * Удаляет лишние теги у сущности
   * 
   * @param array $arrTagIds - массив ID тегов
   */
  private function deleteTags(array $arrTagIds) {
  	Service_Debug::setDebug();
  	if (!$this->id || !$arrTagIds) return false;
  	$stmt = "DELETE FROM `essence_tag`
  	WHERE 
  	  `essence_tag`.`ID_essence` = '" . $this->id . "' AND 
  	  `essence_tag`.`essence_type` = '" . $this->tableName . "' AND
  	  `essence_tag`.`ID_tag` IN (" . join(', ', $arrTagIds) . ");";
  	$objDB = Service_Database::instance();
  	$objDB->query($stmt);
  }
  
  /**
   * Вставляем новые теги к сущности
   * 
   * @param array $arrTags
   */
  private function insertTags(array $arrTags) {
  	Service_Debug::setDebug();
  	if (!$this->id || !$arrTags) return false;
  	$stmtInsert = "";
  	$i = 0;
  	foreach ($arrTags as $tagId) {
  		if ($i++) $stmtInsert .= ", ";
  		$stmtInsert .= "('" . $this->id . "', '" . $tagId . "', '" . $this->tableName . "')";
  	}
  	$stmt = "INSERT INTO `essence_tag` (`ID_essence`, `ID_tag`, `essence_type`) VALUES " . $stmtInsert;
  	$objDB = Service_Database::instance();
  	$objDB->query($stmt);
  }  
  
  /**
   * Вставляем теги в базовую таблицу тегов
   * 
   * @param array $arrTags
   */
  private function insertBaseTags(array $arrTags) {
  	Service_Debug::setDebug();
  	if (!$arrTags) return false;
  	
  	$objDB = Service_Database::instance();
    // Получаем список ID тегов, которые есть в БД хоть у одной сущности
    $stmt = "SELECT `tag`.`tag_name` FROM `tag` WHERE `tag`.`tag_name` IN ('" . join("', '", $arrTags) . "')";
    $arrExist = $objDB->selectSimpleArray($stmt);
  	$arrNewTags = array_diff($arrTags, $arrExist);
  	if (!$arrNewTags) return false;
  	$stmtInsert = "";
  	$i = 0;
  	foreach ($arrNewTags as $tagName) {
  		if ($i++) $stmtInsert .= ", ";
  		$stmtInsert .= "('" . $tagName . "')";
  	}
  	$stmt = "INSERT INTO `tag` (`tag_name`) VALUES " . $stmtInsert;
  	$objDB->query($stmt);
  }
  
	/**
	 * Формирует новое имя для копии сущности
	 * 
	 * @return string - Новое имя 
	 */
	function generateNewNameForACopy() {
		Service_Debug::setDebug();
		$objDB = Service_Database::instance();//Получаем подключение к БД
		$baseName = $this->getField($this->tableName . '_name');
		$pattern = '|( \([0-9]+\))$|i';
		if (preg_match($pattern, $baseName)) {
			$baseName = preg_replace($pattern, '', $baseName);
		} 
		$stmt = "SELECT `" . $this->tableName . "_name` FROM `" . $this->tableName . "` WHERE `" . $this->tableName . "`.`" . $this->tableName . "_name` LIKE '" . $baseName . " (%'";
		$arrNames = $objDB->selectSimpleArray($stmt);
		// Если еще не было копий
		if (!$arrNames) return $baseName . ' (2)';
		
		// Если копии уже были
		$maxCopyId = 1;
		foreach ($arrNames as $name) {
			$currentCopyId = intval(str_replace($baseName . ' (', '', $name));
			if ($currentCopyId > $maxCopyId) $maxCopyId = $currentCopyId;
		}
		return $baseName . ' (' . ($maxCopyId + 1) . ')';
	}

	/**
	 * Добавляет посещение для данной сущности
	 */
	public function addHit() {
		Service_Debug::setDebug();
		if (!$this->id || !isset($this->tableFields['hits'])) return false;
		$objDB = Service_Database::instance();
		return $objDB->update($this->tableName, array('hits' => $this->getField('hits') + 1), array($this->pkName => $this->id));
	}  	
	
}
?>